%% @author Hee Yeon Cho <aposto@nexon.co.kr>
%% @copyright 2009 Nexon Corporation.

%% @doc 
%%      
	
-module (savage_udp_resolver).
-author('aposto@nexon.co.kr').

-behaviour(gen_server).

 
%% gen_server callbacks
-export([init/1, terminate/2, code_change/3,
		handle_call/3, handle_cast/2, handle_info/2]).

%% External exports
-export([start/1, start_link/1, stop/0, restart/1, dev_start/0]).

-export([new_session/2, destroy_session/1]).

-record( state, {udpsock, receiver} ).

%% stage : wait -> send -> ack -> complete
-record( udp_reg, {index, localaddr, localport, remoteaddr, remoteport, stage=wait, start=erlang:now() } ).


start([Port]) ->
	gen_server:start({local, ?MODULE}, ?MODULE, [Port], []).
start_link(Port) ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [Port], []).	
stop() ->
	gen_server:cast( ?MODULE, stop ).
 
 
dev_start() ->
	 
	%inet:getiflist(). 
	start_link(9400),		
	
	timer:sleep(100),
	new_session(21, {127,0,0,1}),	
	timer:sleep(100),	
	gen_server:cast( ?MODULE, {recv, nil, 90} ),
	gen_server:cast( ?MODULE, {recv, nil, 21} ),
	gen_server:cast( ?MODULE, {recv, nil, 21} ),
	gen_server:cast( ?MODULE, {recv, nil, 21} ),
	
	timer:sleep(100),
	stop(),	
	ok.	
	
restart(Args) ->
	stop(),
	start_link(Args).		

 
init([Port]) ->
	%% TODO
	process_flag(trap_exit, true),
	ets:new( udp_reg_db, [ protected, set, named_table, {keypos, #udp_reg.index} ] ),
	error_logger:info_msg("~p starting:~p~n", [?MODULE, Port ]),
	Opts = [binary, {active, false}, {reuseaddr, true}],
	case gen_udp:open( Port, Opts ) of
		{ok, Socket} ->
			{ok, new_receiver(#state{udpsock=Socket})};
		{error, Reason} ->
			{stop, Reason}
	end.	

new_receiver(State=#state{udpsock=Sock}) ->
	Pid = proc_lib:spawn_link( fun() -> loop_recv(Sock) end ),
	{ok, State#state{receiver=Pid}}.

terminate(Reason, State) ->		
	Size = ets:info(udp_reg_db, size),	
	io:format("#terminate ~p:~p:~p:~p~n", [ ?MODULE, Reason, State, Size ]),
	ets:delete( udp_reg_db ),
	gen_udp:close( State#state.udpsock ),
	ok.

code_change(_OldVersion, State, _Extra) -> State.

handle_info({'EXIT', Pid, normal}, State=#state{receiver=Pid}) ->
	{noreply, new_receiver(State)};	
handle_info({'EXIT', Pid, Reason}, State=#state{receiver=Pid}) ->
	error_logger:error_report({?MODULE, ?LINE, {receiver_error, Reason}}),
	timer:sleep(100),
	{noreply, new_receiver(State)};		
handle_info(Info, State) ->
	error_logger:info_report([{'INFO', Info}, {'State', State}]),
	{noreply, State}.
	
handle_call(_Request, _From, State) ->	
	Res = error,
	{reply, Res, State}.
	
handle_cast(stop, State) ->	
	{stop, normal, State};	
handle_cast({new_udp_reg, Index, LocalAddr}, State) ->
	io:format("{new_udp_reg} ~p~n", [State]),
	case ets:insert_new( udp_reg_db, #udp_reg{index=Index, localaddr=LocalAddr} ) of
		true -> noop;
		false -> 
			io:format("{new_udp_reg} error - alreadu exists ~p~n", [Index])
	end,
	{noreply, State};

handle_cast({destroy_udp_reg, Index}, State) ->
	ets:delete(udp_reg_db, Index),
	{noreply, State};

handle_cast({recv, Addr, Bin}, State) ->	
	case ets:lookup(udp_reg_db, Bin) of
		[] -> 
			io:format("# hack ~p~n", [Bin]),
			%hack
			nop;
		[UdpReg] ->
			handle_udp_reg(UdpReg)
	end,
	{noreply, State}.
			
new_session( Index, LocalAddr ) ->
	gen_server:cast( ?MODULE, {new_udp_reg, Index, LocalAddr} ).	

destroy_session( Index ) ->
	gen_server:cast( ?MODULE, {destroy_udp_reg, Index} ).	
	
handle_udp_reg(UdpReg = #udp_reg{stage=wait, index=Index}) ->
	io:format("handle_reg_udp #1 ~p:~p~n", [Index,UdpReg] ),
	ets:update_element( udp_reg_db, Index, { #udp_reg.stage, send} );
		
handle_udp_reg(UdpReg = #udp_reg{stage=send, index=Index}) ->	
	io:format("handle_reg_udp #2 ~p:~p~n", [Index,UdpReg] ),
	%% send sessionman
	ets:delete( udp_reg_db, Index ).
		
%index, s-key, local-udp-addr, 

		
 
 
	
%% Internal API
loop_recv( Sock ) ->
	case gen_udp:recv(Sock, 0) of
		{ok, {Addr, Port, Packet}} ->
			io:format("udp-recv/~p#~p ~p~n", [Sock,self(), Packet]);
		{error, Reason} ->		
			io:format("@@recv error ~p:~p:~p~n", [Sock,self(), Reason])		
	end,
	loop_recv( Sock ).

